package at.jaki.ubb.crawling.appThreads;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;

import at.jaki.ubb.crawling.api.DbFacade;
import at.jaki.ubb.crawling.components.DataInterpreterWrapper;
import at.jaki.ubb.crawling.model.CEEntity;

/**
 * Used defined database is wrapped around with this class (singleton)
 * 
 * @author Attila Jakabfi
 * 
 */
public final class DbWrapper extends AbstractCrawlerThread {

	private static final Logger LOG = Logger.getLogger(DbWrapper.class);

	private static final int INITIAL_SIZE = 100;

	private DbFacade facade;
	private List<String> newIds = Collections.synchronizedList(new ArrayList<String>(INITIAL_SIZE));

	private static DbWrapper instance;

	public static synchronized void init(String facadeImplClass) {

		DbFacade facade = null;
		try {
			Object newInstance = Class.forName(facadeImplClass).newInstance();
			facade = (DbFacade) newInstance;
		} catch (InstantiationException e) {
			LOG.error("Could not instantiate class ".concat(facadeImplClass).concat(". No default constuctor, or the class is abstract."));
			if (LOG.isDebugEnabled()) {
				e.printStackTrace();
			}
		} catch (IllegalAccessException e) {
			LOG.error(e.getClass().getName().concat(" : ") + e.getLocalizedMessage());
			if (LOG.isDebugEnabled()) {
				e.printStackTrace();
			}
		} catch (ClassNotFoundException e) {
			LOG.error(e.getClass().getName().concat(" : ") + e.getLocalizedMessage());
			if (LOG.isDebugEnabled()) {
				e.printStackTrace();
			}
		} catch (RuntimeException e) {
			LOG.error(e.getClass().getName().concat(" : ") + e.getLocalizedMessage());
			if (LOG.isDebugEnabled()) {
				e.printStackTrace();
			}
		}

		instance = new DbWrapper(facade);
	}

	public static DbWrapper getInstance() {
		if (instance == null) {
			throw new IllegalStateException("Facade not initialized");
		}
		return instance;
	}

	private DbWrapper(DbFacade facade) {
		this.facade = facade;
	}

	/**
	 * Persists the given entityi into the databse. Will assign a GUID for the
	 * entity.
	 * 
	 * @param entity entity to be saved.
	 */
	public void persist(CEEntity entity) {
		if (entity == null) {
			LOG.warn("received null value. Will not persist");
			return;
		}
		String id = DbWrapper.nextGUID();
		entity.setGUID(id);
		facade.persist(entity);
		newIds.add(id);
	}

	/**
	 * Persists every entity in the collection. Ids will be assigned here.
	 * 
	 * @param entityCollection collection of entities to be persisted
	 */
	public void persistAll(Collection<CEEntity> entityCollection) {
		List<String> localIds = new ArrayList<String>(entityCollection.size());
		for (CEEntity e : entityCollection) {
			if (e == null) {
				LOG.warn("received null value. Will not persist");
				continue;
			}
			String guid = nextGUID();
			e.setGUID(guid);
			localIds.add(guid);
		}
		facade.persistAll(entityCollection);
		newIds.addAll(localIds);
	}

	@Override
	protected void clear() {
		LOG.info("can not clear the database");
	}

	@Override
	protected void doWork() {
		if (newIds.size() > 0) {
			LOG.debug("Notifing Data interpreter " + newIds);
			DataInterpreterWrapper.getDataInterpreter().notify(newIds);
			newIds.clear();
		}
	}

	private static long idCount = 0;

	/**
	 * Will generate the nextGUID
	 * 
	 * @return a globally unique id
	 */
	public static final synchronized String nextGUID() {
		long time = new Date().getTime();
		return "" + time + (idCount++);
	}

}
